package SSF.OS.OSPFv2.test;

/** 
  * OSPFMonitor.java
  *
  * @author Dirk Jacob
  * @version 0.2.1
  */

import com.renesys.raceway.DML.*;
import java.io.*;
import java.util.*;

/** Implements a tcpDump utility for OSPF protocol traffic. Some of the code for writing
  * binary tcpdump files is "stolen" from SSF.OS.binaryTcpDump. The monitor is used from
  * an IP session, so it also gets all non-OSPF packets. For simplicity, these are simply
  * ignored. 
  */
public class OSPFMonitor implements ProtocolMonitor {
  
  	/** File to write the tcpdump to. */
  	private FileOutputStream outstream;	
  	/** Pointer to the IP session */
  	private ProtocolSession ipSession;
  	/** Name of the binary tcpdump file */
  	private String fileName;
  
  	private byte[] rbuf;
  	private int rct;
  
  	/** Constructor. */
  	public OSPFMonitor() {
  		
  		outstream = null;
  		ipSession = null;
  		fileName = "tcpdump";
  	}

	/** configure the Monitor 
	  * <pre>
	  * ProtocolSession[ name ip use SSF.OS.IP
	  *         monitor[ use SSF.OS.OSPFv2.test.OSPFMonitor
	  *             file <filename>
	  *         ]
	  * ]
	  * </pre>
	  */
  	public void config(ProtocolSession ownerProtocol, Configuration cfg) throws configException {
  		
  		/* TODO: read the interfaces to dump messages from/to from the cfg  - default all */
  		
  		ipSession = ownerProtocol;
  		
  		/* check for a valid invocation of this monitor */
  		if(ipSession == null) {
  			throw new configException("No owner protocol specified!");
  		}
  		
  		if(!(ipSession instanceof IP)) {
  			throw new configException("Owner protocol is not an IP session!");
  		}
  		
  		String name = (String)cfg.findSingle("file");
  		if((name == null) || (name.equals(""))) {
  			/* create a default filename */
  			name = "router";
  			name += ((Host)ipSession.inGraph()).nhi;
  			name += ".tcpdump";
  			
  			/* now replace all : through _ to obtain a valid filename*/
  			name = name.replace(':','_');
  		}
  		fileName = name;
  	}
  	
  	/** Initialize the monitor. This creates the file for the tcpdump.*/
  	public void init() {
  		
  		try {
  			outstream = new FileOutputStream( fileName );
  		} catch ( FileNotFoundException e ) {
  			System.err.println("Cannot create tcpdump file \""+fileName+"\".");
  		}
  		
	}

	/** peek at every IP packet push()ed into IP */
	public void receive(ProtocolMessage packet, ProtocolSession fromSession, ProtocolSession toSession) {
		
		IpHeader ipHdr = (IpHeader)packet;
		
		/* TODO look if the packet "belongs to" an interface which is watched */
		
		if( ipHdr.PROTOCOL_NO == Protocols.OSPF_PRTL_NUM ) {
			dump(new PacketEvent(packet));
		}
  	}
  	
  	//* The stuff from binaryTcpDump which has been adapted to OSPF follows below ***********
  	
  	/** Dump a single packet event */
  	private void dump(PacketEvent pe) {
  		
  		//System.out.println("OSPFMonitor: Dumped packet.");
    	rawPacket(pe,((Host)ipSession.inGraph()).now());
  	}
  	
  	/** Write raw packet bytes to the output stream. */
  	private void rawPacket(PacketEvent pkt, long atTime) {
  		
    	ProtocolMessage msg = pkt.getHeaders(); 

    	double fsec = atTime*1./SSF.Net.Net.frequency;
    	int sec = (int)fsec;
    	int usec = (int)((fsec-sec)*1000000.);

    	synchronized(this) {
      		if (rbuf == null) {
				rbuf = new byte[4096];
				write_raw_file_header();
      		}
    	}

    	if (msg instanceof IpHeader) {

      		IpHeader iphdr = (IpHeader)msg;
			
			/* Only look at OSPF packets here -- the TCP and UDP stuff has been thrown out, dj */
      		if (iphdr.PROTOCOL_NO == Protocols.OSPF_PRTL_NUM ) {
        
        		Packet ospfHeader = (Packet)iphdr.payload();
        		int pktLen = 4 + 20 + ospfHeader.bytecount();
        
        		/* I.  Write the four-word per-packet tcpdump header. */
				rawh(4,sec);               // timestamp: seconds
				rawh(4,usec);              // timestamp: microseconds
				rawh(4, pktLen);                // length of portion present: 11 words 
				rawh(4,1500);              // length of packet (off wire)
	
				/* II. Write the "null" link-level encapsulation header (1 word) */
				raw(4,0);

				/* III.  Write the IP packet header: 5 words. */
				raw(1,(4<<4) + 5);         // IP version (4) and header length (5w)
				raw(1,iphdr.TOS);          // Type of Service
				raw(2,iphdr.bytecount());  // packet octet count
				raw(2,0);                  // datagram id
				raw(2,0);                  // flags and fragment offset
				raw(1,iphdr.TIME_TO_LIVE); // time to live
				raw(1,iphdr.PROTOCOL_NO);  // IP protocol
				raw(2,0);                  // header checksum
				raw(4,iphdr.SOURCE_IP);    // source IP address
				raw(4,iphdr.DEST_IP);      // destination IP address
				raw_commit();
	
		        dump_ospf_packet( ospfHeader );
        	}
    		raw_commit();
  		}
	}
	
	/** Dumps an OSPF packet */
  	private void dump_ospf_packet( Packet ospfHdr ) {

		/* Write the standard OSPF Packet Header */
		raw(1, 2);		   // OSPF Version 2
		raw(1, ospfHdr.type);      // packet type
		raw(2, ospfHdr.bytecount()); // packet length
		raw(4, ospfHdr.router_ID); // router ID
		raw(4, ospfHdr.areaNum);   // area ID
		raw(2, 0);		   // checksum (not implemented)
		raw(2, 0);		   // auth type (not implemented)
		raw(4, 0);		   // authentication (not implemented)
		raw(4, 0);		   // authentication (not implemented)	
		raw_commit();
		
		switch(ospfHdr.type) {
			case Packet.HELLO:
				HelloPacket helloHdr = (HelloPacket)ospfHdr.payload();
		
				raw(4, helloHdr.netMask);
				raw(2, helloHdr.helloInterval);
				raw(1, (helloHdr.options[6] ? 2 : 0));
				raw(1, helloHdr.routerPrio);
				raw(4, helloHdr.routerDeadInterval);
				raw(4, helloHdr.DR);
				raw(4, helloHdr.BDR);
			
				for (Enumeration e = helloHdr.neighborIDs.elements() ; e.hasMoreElements() ;) {
      				raw(4, ((Integer)e.nextElement()).intValue());
    			} 
    			raw_commit();
				break;
			case Packet.DATABASE:
				DD_Packet ddHdr = (DD_Packet)ospfHdr.payload();
				raw(2, ddHdr.interfaceMTU);
				raw(1, getOspfOptions(ddHdr.options));
				byte flags = (byte)(ddHdr.initialize == true ? 0x04 : 0x00);
				flags |= (byte)(ddHdr.more == true ? 0x02 : 0x00);
				flags |= (byte)(ddHdr.isMaster == true ? 0x01 : 0x00);
				raw(1, flags);
				raw(4, ddHdr.DD_seq_num );
				for (Enumeration e = ddHdr.getHeaders().elements(); e.hasMoreElements(); ) {
					LSA l = (LSA)e.nextElement();
					raw(2, l.lsAge);
					raw(1, getOspfOptions(l.options));
					raw(1, l.lsType);
					raw(4, l.lsID);
					raw(4, l.advRouter);
					raw(4, l.lsSeqnum);
					raw(2, 0);
					raw(2, l.bytecount());
					raw_commit();
				}
				raw_commit();
				break;
			case Packet.LSREQUEST:
				LS_RequestPacket lsReqHdr = (LS_RequestPacket)ospfHdr.payload();
				for (Enumeration e = lsReqHdr.getRequests().elements(); e.hasMoreElements(); ) {
					String lsaData = (String)e.nextElement();
				
					StringTokenizer st = new StringTokenizer(lsaData);
				
					if(st.countTokens() == 3) {
						raw(4, Integer.parseInt(st.nextToken()));
						raw(4, IP_s.StrToInt(st.nextToken()));
						raw(4, IP_s.StrToInt(st.nextToken()));
						raw_commit();
					}		
				}
				raw_commit();
				break;
			case Packet.LSUPDATE:
				LS_UpdatePacket lsUpdHdr = (LS_UpdatePacket)ospfHdr.payload();
			
				int numLSA = lsUpdHdr.numberOfLSAs();
				raw(4, numLSA);
			
				for (Enumeration e = lsUpdHdr.getLSAs().elements(); e.hasMoreElements(); ) {
					LSA l = (LSA)e.nextElement();
				
					dump_ospf_lsa( l );
				}
				raw_commit();
				break;
			case Packet.LSACK:
				LS_AckPacket lsAckHdr = (LS_AckPacket)ospfHdr.payload();
			
				for (Enumeration e = lsAckHdr.getHeaders().elements(); e.hasMoreElements(); ) {
					LSA l = (LSA)e.nextElement();
					raw(2, l.lsAge);
					raw(1, getOspfOptions(l.options));
					raw(1, l.lsType);
					raw(4, l.lsID);
					raw(4, l.advRouter);
					raw(4, l.lsSeqnum);
					raw(2, 0);
					raw(2, l.bytecount());
					raw_commit();
				}
				break;
			default:
				int len = ospfHdr.bytecount() - 24;
				if( len > 0 ) {
					for(int i = 0; i < len; i++) 
						raw(1,0);
				}
				raw_commit();
				break;
		}
	}
  
  	/** Dumps a single OSPF LSA */
	private void dump_ospf_lsa( LSA lsa ) {
  	
  		/* first, the common LSA header */
  		raw(2, lsa.lsAge);
  		raw(1, getOspfOptions(lsa.options));
		raw(1, lsa.lsType);
		raw(4, lsa.lsID);
		raw(4, lsa.advRouter);
		raw(4, lsa.lsSeqnum);
		raw(2, 0);
		raw(2, lsa.bytecount()); 
	
		/* now dump the LSA contents */
		switch( lsa.lsType ) {
			case LSA.ROUTER_LSA:
				Router_LSA rtrLSA = (Router_LSA)lsa.payload();
			
				byte flags = (byte)(rtrLSA.isVirtLinkEndPoint ? 0x04 : 0x00);
				flags |= (byte)(rtrLSA.isASBoundaryRouter ? 0x02 : 0x00);
				flags |= (byte)(rtrLSA.isAreaBorderRouter ? 0x01 : 0x00);
			
				raw(1, flags);
				raw(1, 0);
				raw(2, rtrLSA.getNumOfLinks());
			
				for(Enumeration e = rtrLSA.links.elements(); e.hasMoreElements(); ) {
					LinkInfo lnk = (LinkInfo)e.nextElement();
				
					raw(4, lnk.linkID);
					raw(4, lnk.linkData);
					raw(1, lnk.linkType);
					raw(1, lnk.numOfTOSMetrics);
					raw(2, lnk.metric);
				
					/* normally, the TOS metrics would be placed here, but
					   TOS is not implemented, so this is left blank */
				    
					raw_commit();
				}
				break;
			case LSA.NETWORK_LSA:
			case LSA.SUMMARY_NETWORK_LSA:
			case LSA.SUMMARY_ASBR_LSA:
			case LSA.AS_EXTERNAL_LSA:
				/* other LSAs are not implemented yet in OSPF */
			default:
				int len = lsa.bytecount() - 20;
				
				if( len > 0 ) {
					for( int i = 0; i < len; i++ ) {
						raw(1, 0);
					}
				}
				raw_commit();
				break;
		}
  	}

	/** Creates an OSPF options byte from a bool array */
  	private byte getOspfOptions( boolean[] opts ) {
  	
  		byte options = 0x00;
  	
  		for(byte i = 0; i < 8; i++) {
  			if(opts[i] == true) {
  				options |= (byte)(0x01 << (7-i));
  			}
  		}	
  		return options;
  	}

  

	/** Write 1, 2, or 4 bytes to buffer in network byte order */
 	private void raw(int len, long val) {
    	switch(len) {
    		case 1:
      			rbuf[rct++] = (byte)(val);
      			break;
    		case 2:
      			rbuf[rct++] = (byte)(val>>8);
      			rbuf[rct++] = (byte)(val);
      			break;
    		case 4:
      			rbuf[rct++] = (byte)(val >>24);
      			rbuf[rct++] = (byte)(val >>16);
      			rbuf[rct++] = (byte)(val >>8);
      			rbuf[rct++] = (byte)(val );
      			break;
    		default:
      			System.err.println("TCPDUMP: Error: raw write of bad byte count");
    	}
  	}

	/** Write 1, 2, or 4 bytes to buffer in host order */
  	private void rawh(int len, long val) {
    	switch(len) {
    		case 1:
      			rbuf[rct++] = (byte)(val);
      			break;
    		case 2:
      			rbuf[rct++] = (byte)(val);
      			rbuf[rct++] = (byte)(val>>8);
      			break;
    		case 4:
      			rbuf[rct++] = (byte)(val );
      			rbuf[rct++] = (byte)(val >>8);
      			rbuf[rct++] = (byte)(val >>16);
      			rbuf[rct++] = (byte)(val >>24);
      			break;
    		default:
      			System.err.println("TCPDUMP: Error: raw write of bad byte count");
    	}
  	}

	/** Write buffered bytes to the printstream */
  	private void raw_commit() {
	    try {
    		outstream.write(rbuf,0,rct);
      		outstream.flush();
    	} catch (Exception e) {
      		System.err.println(e);
      		System.exit(-1);
    	}
    	rct =0;
  	}

	/** Write the generic file header for TCP binary dump files */
  	private void write_raw_file_header() {
    	final int MAGIC = 0xA1B2C3D4; 
    	final int MAJOR = 2, MINOR = 4; // making it up as we go along
    	final int ZONE = 0, SIGFIGS = 0, SNAPLEN = 1500;
    	final int LINKTYPE = 0;  // Ethernet encapsulation 
    
	    rct = 0;
    	rawh(4,MAGIC);           // magic number for tcpdump files
    	rawh(2,MAJOR);           // major version number
    	rawh(2,MINOR);           // minor version number
    	rawh(4,ZONE);            // gmt to local correction
    	rawh(4,SIGFIGS);         // accuracy of timestamps
    	rawh(4,SNAPLEN);         // max length saved portion of each pkt
    	rawh(4,LINKTYPE);        // data link type
    	raw_commit();
  	} 
}
